/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package edu.isi.pegasus.common.util;

import edu.isi.pegasus.common.logging.LogManager;
import edu.isi.pegasus.common.logging.LogManagerFactory;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A utility class that allows us to determine condor version.
 * 
 * @author Karan Vahi
 */
public class CondorVersion {

    /**
     * Predefined Constant for condor version 7.1.0
     */
    public static final long v_7_1_0 = CondorVersion.numericValue( "7.1.0" );
            
    
    /**
     * Predefined Constant for condor version 7.1.2
     */
    public static final long v_7_1_2 = CondorVersion.numericValue( "7.1.2" );
            
    
    /**
     * Predefined Constant for condor version 7.1.3
     */
    public static final long v_7_1_3 = CondorVersion.numericValue( "7.1.3" );
    
    /**
     * The maximum number of components version can have. MAJOR, MINOR, PATCH
     */
    private static final  int MAX_NUMBER_OF_VERSION_COMPONENTS = 3;
    
    /**
     * The maximum number of digits each component of version can have.
     */
    private static final  int MAX_VERSION_PRECISION = 2;
    
    
    /**
     * The condor version command to be executed.
     */
    public static final String CONDOR_VERSION_COMMAND = "condor_version";
    
    
     /**
      * Store the regular expressions necessary to parse the output of
      * condor_version. The rule for the format is
      *
      * $CondorVersion: 7.4.1 Dec 17 2009 <ANY_ARBITRARY_STRING> $
      * where <ANY_ARBITRARY_STRING> may or may not be there, and can include spaces but is
      * really completely arbitrary.
      *
      * e.g. $CondorVersion: 7.1.0 Apr  1 2008 BuildID: 80895$
      */
    private static final String mRegexExpression =
//                                     "\\$CondorVersion:\\s*([0-9][\\.][0-9][\\.][0-9])[a-zA-Z:0-9\\s]*\\$";
                                         //"\\$CondorVersion:\\s*([0-9][\\.][0-9][\\.][0-9])[\\w\\W\\s]*\\$";
                                         "\\$CondorVersion:\\s*([0-9][\\.][0-9][\\.][0-9])[\\p{ASCII}\\s]*\\$";


    /**
     * Stores compiled patterns at first use, quasi-Singleton.
     */
    private static Pattern mPattern = null;
   
    /**
     * Converts a string into the corresponding integer value.
     * 
     * @param version
     * 
     * @return int value of the version, else -1 in case of null version 
     *         or incorrect formatted string
     * 
     * @deprecated
     */
    public static int intValue( String version ){
        int result = 0;
        if( version == null ){
            return -1;
        }
        
        //split on .
        try{
            String[] subs = version.split( "\\." );
            int index = subs.length;
            for( int i = 0, y = subs.length - 1; y >= 0; y--,i++){
                result += (int) (Math.pow(10, y) * (Integer.parseInt(subs[i])));
            }
        }
        catch( NumberFormatException nfe ){
            result = -1;
        }
        
        return result;
    }
    
    /**
     * Converts a string into the corresponding numeric value.
     * 
     * @param version in form of major.minor.patch. You can opt to omit the 
     *                minor and patch versions if you want
     * 
     * @return float value of the version, else -1 in case of null version 
     *         or incorrect formatted string
     */
    public static long numericValue( String version ){
        long result = 0;
        if( version == null ){
            return -1;
        }
        
        //we are converting to XX.XX.XX 
        //add extra padding that is leading zero if only one digit
        char[] arr = new char[6];
        
        //split on .
        try{
            String[] subs = version.split( "\\." );
            int y = subs.length;
            if ( y > CondorVersion.MAX_NUMBER_OF_VERSION_COMPONENTS ){
                throw new IllegalArgumentException( 
                        "Only version numbers with max two dots are accepted i.e ( MAJOR.MINOR.PATCH ) " + version );
            }
            
            int i = 0;
           
                //for each sub convert to a two digit form
                for( int z = 0; z < y; z++ ){
                    
                    //compute the sub length
                    int len = subs[z].length();
                    if( len > CondorVersion.MAX_VERSION_PRECISION ){
                        throw new IllegalArgumentException( "Only two digit precision is allowed in version numbers "  + version);
                    }
                    
                    //add leading zeros if required
                    for ( int d = CondorVersion.MAX_VERSION_PRECISION - len; d > 0; d--){
                        arr[i++] = '0';
                    }
                    
                    //copy into arr the sub[z]
                    for( int d = 0; d < len; d++){
                        char ch = subs[z].charAt( d );
                        if( !Character.isDigit(ch) ){
                            throw new IllegalArgumentException( "Non digit specified in version "  + version);
                        }
                        arr[i++] = ch;
                    }
                }
            
            //add trailing zeroes if required
            while( i < 6 ){  
                arr[i++] = '0';
            }
            
        }
        catch( NumberFormatException nfe ){
            result = -1;
        }
        
        return Long.parseLong( new String(arr) );
    }
    
    /**
     * The default logger.
     */
    private LogManager mLogger;
    
    /**
     * Factory method to instantiate the class.
     *
     * @return  instance to the class
     */
    public static CondorVersion getInstance( ){
        return getInstance( null );
    }
    
    /**
     * Factory method to instantiate the class.
     * 
     * 
     * @param logger   the logger object
     *
     * @return  instance to the class.
     */
    public static CondorVersion getInstance( LogManager logger ){
        if( logger == null ){
            logger = LogManagerFactory.loadSingletonInstance();
        }
        return new CondorVersion( logger );
    }
    
    /**
     * The default constructor.
     * 
     * @param logger   the logger object
     */
    private CondorVersion( LogManager logger ){
        mLogger = logger;
        if( mPattern == null ){
             mPattern = Pattern.compile( mRegexExpression );
         }
    }
    
    /**
     * Returns the condor version parsed by executing the condor_version 
     * command. 
     * 
     * @return the version number as int else -1 if unable to determine.
     */
    public long numericValue(){
        long result = -1;
        try{
            result = CondorVersion.numericValue( version() );
        }
        catch( Exception e ){
            mLogger.log("Exception while parsing condor_version ", e,
                        LogManager.ERROR_MESSAGE_LEVEL);
        }
        return result;
    }
    
    /**
     * Returns the condor version parsed by executing the condor_version 
     * command. 
     * 
     * @return the version number as String else null if unable to determine.
     */
    public String version(){
        String version = null;
        
        try{
            //set the callback and run the grep command
            CondorVersionCallback c = new CondorVersionCallback( );
            Runtime r = Runtime.getRuntime();
            Process p = r.exec( CONDOR_VERSION_COMMAND );
            
            //Process p = r.exec( CONDOR_VERSION_COMMAND );

            //spawn off the gobblers
            StreamGobbler ips = new StreamGobbler(p.getInputStream(), c);
            StreamGobbler eps = new StreamGobbler(p.getErrorStream(),
                                                  new StreamGobblerCallback(){
                                                      //we cannot log to any of the default stream
                                                      LogManager mLogger = this.mLogger;
                                                      public void work(String s){
                                                          mLogger.log("Output on stream gobller error stream " +
                                                                      s,LogManager.DEBUG_MESSAGE_LEVEL);
                                                      }
                                                     });
            ips.start();
            eps.start();

            //wait for the threads to finish off
            ips.join();
            version = c.getVersion();
            eps.join();

            //get the status
            int status = p.waitFor();
            if( status != 0){
                mLogger.log("Command " + CONDOR_VERSION_COMMAND + " exited with status " + status,
                            LogManager.WARNING_MESSAGE_LEVEL);
            }

        }
        catch(IOException ioe){
            mLogger.log("IOException while determining condor_version ", ioe,
                        LogManager.ERROR_MESSAGE_LEVEL);
        }
        catch( InterruptedException ie){
            //ignore
        }
        
        mLogger.log( "Condor Version as string " + version, LogManager.DEBUG_MESSAGE_LEVEL );
        return version;
    }
    
    /**
     * An inner class, that implements the StreamGobblerCallback to determine
     * the version of Condor being used.
     *
     */
    private static class CondorVersionCallback implements StreamGobblerCallback{

        
       

        /**
         * The version detected.
         */
        private String mVersion;

        /**
         * The Default Constructor
         */
        public CondorVersionCallback(  ){
            mVersion = null;
        }

        /**
         * Callback whenever a line is read from the stream by the StreamGobbler.
         * Counts the occurences of the word that are in the line, and
         * increments to the global counter.
         *
         * @param line   the line that is read.
         */
        public void work( String line ){
            Matcher matcher = mPattern.matcher( line );
            if( matcher.matches( ) ){
                mVersion = matcher.group( 1 );
            }
        }

        /**
         * Returns the condor version detected.
         *
         * @return the condor version else null
         */
        public String getVersion(){
            return mVersion;
        }

        

    }

    /**
     * The main program to test.
     *
     * @param args
     */
    public static void main( String[] args ){
        LogManager logger =  LogManagerFactory.loadSingletonInstance();
        CondorVersion cv = CondorVersion.getInstance();
        
        logger.logEventStart( "CondorVersion", "CondorVersion", "Version");
        System.out.println( "Condor Version is " + cv.version() );
        
        System.out.println( "10.0.0 is " + CondorVersion.numericValue( "10.0.0") );
        System.out.println( "7.1.2 is " + CondorVersion.numericValue( "7.1.2") );
        System.out.println( "7.1.18 is " + CondorVersion.numericValue( "7.1.18" ) ); 
        System.out.println( "7.1.19 is " + CondorVersion.numericValue( "7.1.19" ) ); 
        System.out.println( "6.99.9 is " + CondorVersion.numericValue( "6.99.9" ) ); 
        System.out.println( "7 is " + CondorVersion.numericValue( "7.2.2" ) );
        logger.logEventCompletion();

        //some sanity checks on the Regex
        String version = "$CondorVersion: 7.4.1 Dec 17 2009 UWCS-PRE $";
        Matcher matcher = cv.mPattern.matcher( version );
        if( matcher.matches() ){
            System.out.println( "Version for " + version + " is " + matcher.group( 1 ));
        }


        version = "$CondorVersion: 7.4.1 Dec 17 2009 BuildID: 204351 $";
        matcher = cv.mPattern.matcher( version );
        if( matcher.matches() ){
            System.out.println( "Version for " + version + " is " + matcher.group( 1 ));
        }
    }
}
